/**
 * @file    Expression classname is undefined on line 2, column 15 in Templates/Classes/Class.java.
 * @author  Haseeb Yousaf
 * @see     LICENSE (MIT style license file)
 * @version 1.0
 * @date    Nov 13, 2011 7:26:27 PM
 */
package carrello.model.persist;

import org.apache.log4j.Logger;
import java.util.Set;
import javax.persistence.*;
import org.hibernate.Criteria;
import org.hibernate.*;
import org.hibernate.criterion.Projections;

import static java.lang.System.out;
import static carrello.util.hiberutil.getSessionFactory;

public class EntityManagerImpl implements IEntityManager 
{
    
     private static final Logger log = Logger.getLogger(EntityManagerImpl.class); 
    /**
     * Hibernate SessionFactory singleton, created upon request, in
     * {@link #init()}.
     */
    private static SessionFactory sessionFactory;
    private static Session session;
    
    private boolean               uowStarted               = false;
    
    static {
        session = getSessionFactory().openSession();
    }
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    /**
     * Init hibernate {@link SessionFactory}, this takes a while, but only needs
     * to be done once per application instance.
     */
    public static void init() 
    {
        sessionFactory = getSessionFactory();
    } // init
    
    public static SessionFactory getEntityManagerFactory() {

        if (sessionFactory == null) {
            log.info("sessionFactory == null, dynamically initialising one...");
            init();
        }
        return sessionFactory;
    }
    
        /**
     * Rolls back current transaction, doesn't close Hibernate {@link Session}.
     * {@inheritDoc}
     */
    public void abortUnitOfWork() {

        Transaction tx = getHibernateSession().getTransaction();
        if (tx == null)
            
            return;
        
        if (!tx.isActive()) {
            log.error("a transaction is not active, cannot be aborted");
            return;
        }
        
        try {
            log.info("aborting current transaction, trying to rollback...");
            tx.rollback();
            
            
            log.info("transaction successfully rolled back");
        } catch (Exception ex) {
            log.error("Could not rollback transaction -exception caught during rollback:", ex);
        } finally {
            uowStarted = false;
        }
    }
    
    /**
     * Opens new (Hibernate-wrapped) transaction. {@inheritDoc}
     */
    public void beginUnitOfWork() {

        log.info("starting database transaction");
        getHibernateSession().getTransaction().begin();
        uowStarted = true;
    }
    
    /**
     * Commits current transaction, rolls back if commit throws Exception.
     * {@inheritDoc}
     */
    public void endUnitOfWork() {

        boolean success = false;
        try {
            if (getHibernateSession().getTransaction().isActive()) {
                log.info("*** committing database transaction ***");
                getHibernateSession().getTransaction().commit();
//                getHibernateSession().close();
            } else {
                log.error("no active transaction, cannot be ended");
            }
            success = true;
        } finally {
            if (!success)
            {
                abortUnitOfWork();
                log.warn("(closing hibernate session)");
                getHibernateSession().close();
                uowStarted = false;
            }
        }
    }
    
    /**
     * Returns the current Hibernate {@link Session}, or creates one if
     * necessary.
     * 
     * @see {@link SessionFactory}
     */
    public Session getHibernateSession() {
        return session;
    }
    public <T> int countAll(Class<T> c) {

        Object count = getHibernateSession().createCriteria(c)
                       .setProjection(Projections.rowCount()).uniqueResult();
        
        if (count == null)
            return 0;
        
        if (count instanceof Integer) {
            return ((Integer) count).intValue();
        } else if (count instanceof Long) {
            return ((Long) count).intValue();
        } else
            return 0;
    }
    
    /* @see EntityManager#createNew(Class) */
    public <T> T createNew(Class<T> c) {

        return __instance_of(c);
    }
    
     /* instantiates an instance of passed class; absorbs exceptions. */
    private static final <T> T __instance_of(Class<T> c) {

        T entity = null;
        try {
            entity = c.newInstance();
        } catch (java.lang.InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        
        assert entity != null;
        
        return entity;
    }
    /* @see EntityManager#flush() */
    public void flush() {

        if (getHibernateSession().getTransaction().isActive()) {
            log.info("*** manually flushing data to data store ***");
            getHibernateSession().flush();
        } else {
            log.error("no active transaction, cannot flush data to store.");
        }
    }
    
    /* @see EntityManager#createQuery(Class) */
    public <T> Criteria createQuery(Class<T> c) {

        return getHibernateSession().createCriteria(c);
    }
    
    /* @see EntityManager#getQuery(String) */
    public org.hibernate.Query getQuery(String name_of_query) {

        return getHibernateSession().getNamedQuery(name_of_query);
    }
    
    /* @see EntityManager#lookup(Class,Serializable) */
    // ^^^ unavoidable, due to lack of genericity in Hibernate lib
    public <T> T lookup(Class<T> c, int object_id) throws Exception {

        try {
            Session session = getHibernateSession();
            T entity = (T) session.get(c, object_id);
            return entity;
        } catch (NonUniqueObjectException e) {
            __log_exception(e, c);
            throw e;
        }
    }
      private final void __log_exception(NonUniqueObjectException e, Object entity) {

        log.warn("Caught NonUniqueObjectException while working with "
                + entity.getClass().getName()
                + ". This often means that you need to (re-)implement "
                + "the equals(Object) & hashCode() methods in this class "
                + "so that objects in the session can be compared for "
                + "*equality* rather than *identity*.", e);
    }
    
       /**
     * Attempts to pre-fetch most of the data for the given object, including
     * each of its properties and associations. If the passed object is a
     * {@link Collection} then the whole Collection will be pre-fetched.
     */
    public <T> void populate(T object) {

        Hibernate.initialize(object);
    }
    
    /* @see EntityManager#lookup(T,Serializable) */
    public <T> void lookup(T destinationObject, int objectId) {

        if (log.isDebugEnabled()) {
            log.debug("attempting to populate existing object "
                    + destinationObject + " with id=" + objectId);
        }
        
        try {
            Session session = getHibernateSession();
            session.load(destinationObject, objectId);
        } catch (NonUniqueObjectException e) {
            __log_exception(e, destinationObject);
            throw e;
        }
    }
        public <T> void revert(T entity) {

        Session s = getHibernateSession();
        s.evict(entity);
    }
    
    /* @see EntityManager#remove(T) */
    public <T> void remove(T entity) {

        if (log.isDebugEnabled())
            log.debug("attempting to remove (detach) object " + entity);
        
        Session s = getHibernateSession();
        s.delete(entity);
    }
    
   /* @see EntityManager#removeAll */
    public <T> void removeAll(Set<? extends T> entities) {

        for (T e : entities)
            remove(e);
    }
    
    /* @see EntityManager#store(T) */
    public <T> void store(T entity) {

        if (log.isDebugEnabled())
            log.debug("attempting to store (make persistent) object " + entity);
        
        Session s = getHibernateSession();
        try {
            s.save(entity);
            // s.saveOrUpdate( entity );
        } catch (NonUniqueObjectException ex) {
            log.warn("passed " + entity.getClass().getSimpleName()
                    + " was originally loaded in a different Session"
                    + "; attempting to merge changes...", ex);
            
            s.merge(entity);
            log.debug("changes merged ok");
        }
    }
    public <T> void merge(T entity) {

        if (log.isDebugEnabled())
            log.debug("attempting to merge object of " + entity.getClass());
        Session s = getHibernateSession();
        s.merge(entity);
    }
    
    public <T> void storeAll(Set<? extends T> entities) {

        for (T e : entities)
            store(e);
    }
     /* @see EntityManager#update(T) */
    public <T> void update(T entity) {

        if (log.isDebugEnabled())
            log.debug("attempting to update object " + entity);
        
        Session s = getHibernateSession();
        s.update(entity);
    }
    public <T> void refresh(T entity) {

        if (log.isDebugEnabled())
            log.debug("attempting to refresh object of " + entity.getClass());
        Session s = getHibernateSession();
        s.refresh(entity);
    }
    
    
    /**
     * Testing the connectivity through main method
     * @param args 
     */
    public static void main (String[] args) {
        out.println("Before... ");
//        getHibernateSession();
        out.println("After... ");
    }
}
    


    
